{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "请点击[此处](https://ai.baidu.com/docs#/AIStudio_Project_Notebook/a38e5576)查看本环境基本用法.  <br>\n",
    "Please click [here ](https://ai.baidu.com/docs#/AIStudio_Project_Notebook/a38e5576) for more detailed instructions. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "# 『AI达人创造营』大作业--使用paddlex完成口罩识别任务"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "# 1.环境安装 \n",
    "本次任务使用paddlex套件，首先下载安装paddlex"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# 安装paddlex\r\n",
    "!pip install paddlex -i https://mirror.baidu.com/pypi/simple"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "# 2.数据集准备"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## 2.1 解压数据集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "!unzip -oq /home/aistudio/data/data103743/objDataset.zip"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## 2.2 数据集简介"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 2.2.1 数据集加载和预处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# 利用paddlex自带的工具进行数据集划分\r\n",
    "!paddlex --split_dataset \\\r\n",
    "         --format VOC \\\r\n",
    "         --dataset_dir /home/aistudio/objDataset/facemask/ \\\r\n",
    "         --val_value 0.1 \\\r\n",
    "         --test_value 0.1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 2.2.2 数据集概览"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import os\r\n",
    "import numpy as np\r\n",
    "import xml.etree.ElementTree as ET\r\n",
    "\r\n",
    "      \r\n",
    "def parse_xml(xml_file):\r\n",
    "    tree = ET.parse(xml_file)\r\n",
    "    objs = tree.findall(\"object\")\r\n",
    "    image_w = float(tree.find('size').find('width').text)\r\n",
    "    image_h = float(tree.find('size').find('height').text)\r\n",
    "    gt_bbox = np.zeros((len(objs), 4), dtype=np.float32)\r\n",
    "    gt_classes = []\r\n",
    "    # 读取一个xml文件所有的object信息\r\n",
    "    for i, obj in enumerate(objs):\r\n",
    "        gt_classes.append(obj.find('name').text)\r\n",
    "        # x是width方向，y是height方向\r\n",
    "        xmin = float(obj.find('bndbox').find('xmin').text)\r\n",
    "        ymin = float(obj.find('bndbox').find('ymin').text)\r\n",
    "        xmax = float(obj.find('bndbox').find('xmax').text)\r\n",
    "        ymax = float(obj.find('bndbox').find('ymax').text)\r\n",
    "        xmin = max(0, xmin)\r\n",
    "        ymin = max(0, ymin)\r\n",
    "        xmax = min(image_w, xmax)\r\n",
    "        ymax = min(image_h, ymax)\r\n",
    "        # 转换为xywh的形式，要注意w和h的计算需要+1\r\n",
    "        gt_bbox[i] = [(xmax + xmin) / 2.0, (ymax + ymin) / 2.0, xmax - xmin + 1, ymax - ymin + 1]\r\n",
    "    return image_w, image_h, gt_bbox, gt_classes\r\n",
    "\r\n",
    "def read_annotations(file_path):\r\n",
    "    annotation_files = os.listdir(file_path)\r\n",
    "    image_area = {}\r\n",
    "    obj_classes = {}\r\n",
    "    for file_name in annotation_files:\r\n",
    "        file_name = os.path.join('/home/aistudio/objDataset/facemask/Annotations', file_name)\r\n",
    "        # print(file_name)\r\n",
    "        image_w, image_h, gt_bbox, gt_classes = parse_xml(file_name)\r\n",
    "        if (image_w, image_h) in image_area.keys():\r\n",
    "            image_area[image_w, image_h] += 1\r\n",
    "        else:\r\n",
    "            image_area[image_w, image_h] = 1\r\n",
    "        for gt_class in gt_classes:\r\n",
    "            if gt_class in obj_classes.keys():\r\n",
    "                obj_classes[gt_class] += 1\r\n",
    "            else:\r\n",
    "                obj_classes[gt_class] = 1\r\n",
    "    \r\n",
    "    print('image_area', image_area)\r\n",
    "    print('obj_classes', obj_classes)\r\n",
    "read_annotations('/home/aistudio/objDataset/facemask/Annotations')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "口罩数据集存在不平衡的现象，'with_mask'为3232，'mask_weared_incorrect'为123, 'without_mask'为717较少，后两者数据较少。\n",
    "图片区间大约为200~600之间，高度区间大约为200~400之间。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "# 3.模型训练\n",
    "模型采用paddlex中的ppyolo模型实现，在训练时采用了多种数据增强方式，包括MixupImage、RandomDistort、RandomExpand、RandomCrop、RandomHorizontalFlip、Normalize"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import paddlex as pdx\r\n",
    "from paddlex.det import transforms\r\n",
    "\r\n",
    "# 定义训练和验证时的transforms\r\n",
    "train_transforms = transforms.Compose([\r\n",
    "    transforms.MixupImage(mixup_epoch=250),\r\n",
    "    transforms.RandomDistort(),\r\n",
    "    transforms.RandomExpand(fill_value=[123.675, 116.28, 103.53]),\r\n",
    "    transforms.RandomCrop(),\r\n",
    "    transforms.RandomHorizontalFlip(),\r\n",
    "    transforms.Resize(target_size=608, interp='RANDOM'),\r\n",
    "    transforms.Normalize(\r\n",
    "            mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\r\n",
    "])\r\n",
    "\r\n",
    "eval_transforms = transforms.Compose([\r\n",
    "    transforms.Resize(\r\n",
    "        target_size=608, interp='CUBIC'),\r\n",
    "    transforms.Normalize(\r\n",
    "            mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\r\n",
    "])\r\n",
    "\r\n",
    "# 定义训练和验证所用的数据集\r\n",
    "train_dataset = pdx.datasets.VOCDetection(\r\n",
    "    data_dir='/home/aistudio/objDataset/facemask',\r\n",
    "    file_list='/home/aistudio/objDataset/facemask/train_list.txt',\r\n",
    "    label_list='/home/aistudio/objDataset/facemask/labels.txt',\r\n",
    "    transforms=train_transforms,\r\n",
    "    shuffle=True)\r\n",
    "\r\n",
    "eval_dataset = pdx.datasets.VOCDetection(\r\n",
    "    data_dir='/home/aistudio/objDataset/facemask',\r\n",
    "    file_list='/home/aistudio/objDataset/facemask/val_list.txt',\r\n",
    "    label_list='/home/aistudio/objDataset/facemask/labels.txt',\r\n",
    "    transforms=eval_transforms,\r\n",
    "    shuffle=False)\r\n",
    "\r\n",
    "# 初始化模型，并进行训练\r\n",
    "num_classes = len(train_dataset.labels)\r\n",
    "model = pdx.det.PPYOLO(num_classes=num_classes, backbone='ResNet50_vd_ssld')\r\n",
    "\r\n",
    "model.train(\r\n",
    "    num_epochs=400,\r\n",
    "    train_dataset=train_dataset,\r\n",
    "    train_batch_size=8,\r\n",
    "    eval_dataset=eval_dataset,\r\n",
    "    learning_rate=0.005 / 12,\r\n",
    "    warmup_steps=1000,\r\n",
    "    warmup_start_lr=0.0,\r\n",
    "    save_interval_epochs=50,\r\n",
    "    lr_decay_epochs=[243, 324],\r\n",
    "    save_dir='output/ppyolo_r50vd_dcn',\r\n",
    "    use_vdl=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## 3.1 模型训练结果\n",
    "训练了400个epoch后的模型结果\n",
    "\n",
    "2021-08-13 17:55:13 [INFO]\t[EVAL] Finished, Epoch=400, bbox_map=73.253754 .\n",
    "\n",
    "2021-08-13 17:55:18 [INFO]\tModel saved in output/ppyolo_r50vd_dcn/best_model.\n",
    "\n",
    "2021-08-13 17:55:20 [INFO]\tModel saved in output/ppyolo_r50vd_dcn/epoch_400.\n",
    "\n",
    "2021-08-13 17:55:20 [INFO]\tCurrent evaluated best model in eval_dataset is epoch_400, bbox_map=73.25375440343052"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "# 4.模型预测"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import os\r\n",
    "# 设置置信度阈值\r\n",
    "score_threshold = 0.9\r\n",
    "model_dir = 'output/ppyolo_r50vd_dcn/best_model/'\r\n",
    "save_dir = '/home/aistudio/visualize/predict'\r\n",
    "img_file = '/home/aistudio/objDataset/facemask/JPEGImages/maksssksksss306.png'\r\n",
    "if not os.path.exists(save_dir):\r\n",
    "    os.makedirs(save_dir)\r\n",
    "    \r\n",
    "model = pdx.load_model(model_dir)\r\n",
    "res = model.predict(img_file)\r\n",
    "pdx.det.visualize(img_file, res, threshold=score_threshold, save_dir=save_dir)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "# 5.结果展示\n",
    "![predict result](visualize/predict/visualize_maksssksksss306.png)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "PaddlePaddle 2.1.2 (Python 3.5)",
   "language": "python",
   "name": "py35-paddle1.2.0"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
